/**
* This is the primary class for the Parallaxer library.
*
* @author Timothy Jordan, captaintimothy@gmail.com
*/
package {
  import flash.display.Sprite;
  import flash.events.MouseEvent;
  import flash.events.Event;
  import flash.geom.Point;
  import flash.display.Stage;
  
  import ParallaxerItem;
  
  public class Parallaxer extends Sprite {
    private var _parallaxerItems:Array;
    private var _initialized:Boolean = false;
    private var _mousePosition:Point = null;
    private var _adopt:Boolean = true;
    private var _stage:Stage = null;
    private var _animating:Boolean = false;
    private var _mouseActivated:Boolean = true;
    private var _animateAmount:Point = null;
    
    public var depthMultiplier:Number = .1;
    
    public function Parallaxer(stage:Stage, adopt:Boolean = true):void {
      _adopt = adopt;
      _stage = stage;
      name = "parallaxer";
      
      _parallaxerItems = new Array();
      _stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMove);
    }
    
    /**
    * Adds an item to the parallaxed list.  Every item on the list will
    * be moved according to its depth at the parallax trigger.
    *
    * @param  item  The item to be parallaxed
    * @param  depth The depth at which the item should appear
    * @param  bound Whether to bound the parallaxing if the object's offscreen
    *               edge enters the viewable area.
    */
    public function addItem(item:Sprite, depth:Number, bound:Object = null):void {
      _parallaxerItems.push(new ParallaxerItem(item, depth, _stage, _adopt, bound));
      sort();
    }
    
    public function startAnimation(animateAmount:Point):void {
      _animateAmount = animateAmount;
      _animating = true;
      _stage.addEventListener(Event.ENTER_FRAME, tick);
    }
    
    public function stopAnimation():void {
      if (_animating) {
        _animating = false;
        _stage.removeEventListener(Event.ENTER_FRAME, tick);
      }
    }
    
    public function set mouseActivated(mouseActivated:Boolean):void {
      if (_mouseActivated && !mouseActivated) {
        _stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMove);
      } else if (!_mouseActivated && mouseActivated) {
        _stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMove);
      }
      
      _mouseActivated = mouseActivated;
    }
    
    public function get mouseActivated():Boolean {
      return _mouseActivated;
    }
    
    private function sort():void {
      _initialized = true;
      _parallaxerItems.sortOn("depth");
      
      for each (var item:ParallaxerItem in _parallaxerItems) {
        if (contains(item)) {
          removeChild(item);
        }
        
        addChild(item);
      }
    }
    
    private function mouseMove(event:MouseEvent):void {
      if (!_animating) {
        if (_mousePosition == null) {
          _mousePosition = new Point(event.stageX, event.stageY);
        } else {
          shiftItems(_mousePosition.x - event.stageX, _mousePosition.y - event.stageY);
          _mousePosition.x = event.stageX;
          _mousePosition.y = event.stageY;
        }
      }
    }
    
    private function tick(event:Event):void {
      shiftItems(_animateAmount.x, _animateAmount.y);
    }
    
    //tj: change this to recursion for more efficiency, also doesn't shift up until bound
    /*private function shiftItems(dx:Number, dy:Number):Boolean {
      for each (var item:ParallaxerItem in _parallaxerItems) {
        var itemDx:Number = dx * item.depth * depthMultiplier;
        var itemDy:Number = dy * item.depth * depthMultiplier;
        
        if (item.bounded(itemDx, itemDy)) {
          trace("yes");
        } else {
          trace("no");
        }
        //if (!item.shift(dx * item.depth * depthMultiplier, dy * item.depth * depthMultiplier)) {
        if (!item.shift(itemDx, itemDy)) {
          return false;
        }
      }
      
      return true;
    }*/
    private function shiftItems(dx:Number, dy:Number, index:uint = 0):Boolean {
      if (index >= _parallaxerItems.length) {
        return true;
      }
      
      // Calculate delta per item
      var itemDx:Number = dx * _parallaxerItems[index].depth * depthMultiplier;
      var itemDy:Number = dy * _parallaxerItems[index].depth * depthMultiplier;
      
      // Check to see if we are within bounds
      if (_parallaxerItems[index].bounded(itemDx, itemDy)) {
        
        // Recurse so that nothing moves unless everything can
        if (shiftItems(dx, dy, index + 1)) {
          // Actually shift this item
          _parallaxerItems[index].shift(itemDx, itemDy);
          return true;
        } else {
          // Some item down the chain will be out-of-bounds by this move
          // So, we cancel the move
          return false;
        }
      } else {
        // This item will be out-of-bounds by this move
        // So, we cancel the move
        return false;
      }
    }
  }
}